package momo.app.structure;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Random;
import java.util.StringTokenizer;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class Message
{
	static public final String USER_DATA = "USER_DATA";
	static public final String MISC = "MISC";
	static public final String JOIN = "JOIN";
	static public final String JOIN_REPLY = "JOIN_REPLY";
	static public final String GET_PEERS = "GET_PEERS";
	static public final String GET_PEERS_REPLY = "GET_PEERS_REPLY";
	
//	static public final String ROOT_ELECTION = "ROOT_ELECTION";
	static public final String GENERAL_ELECTION = "GENERAL_ELECTION";
	static public final String NEW_STRUCTURE = "NEW_STRUCTURE";
	static public final String RESTRUCTURE_REQUIRED = "RESTRUCTURE_REQUIRED";
	
	static public final String LATENCY_MEASURE = "LATENCY_MEASURE";
	static public final String LATENCY_MEASURE_REPLY = "LATENCY_MEASURE_REPLY";
//	static public final String KIND_LEAVE = "KIND_LEAVE";
//	static public final String UNKIND_LEAVE = "UNKIND_LEAVE";
	static public final String CLOSE_CONNECTION = "CLOSE_CONNECTION";
	
	static public final String NO_FORWARD = "NO_FORWARD";
	static public final String FORWARD_UP = "FORWARD_UP";
	static public final String FORWARD_DOWN = "FORWARD_DOWN";
	static public final String BROADCAST = "BROADCAST";

	static public final String PING = "PING";
	static public final String LINKUP = "LINKUP";
	static public final String LINKUP_ACK = "LINKUP_ACK";
	static public final String NEW_PEER_LINKUP = "NEW_PEER_LINKUP";
	static public final String UPDATE_PEER_LINK = "UPDATE_PEER_LINK";
	static public final String NEW_PARENT = "NEW_PARENT";
	static public final String NEW_PARENT_ACK = "NEW_PARENT_ACK";
	static public final String MESSAGE_REQUEST = "MESSAGE_REQUEST";
	static public final String MESSAGE_REPLY = "MESSAGE_REPLY";
	static public final String RESET_STATE = "RESET_STATE";
	
	static public final String STAB_MAP_REQUEST = "STAB_MAP_REQUEST";
	static public final String STAB_MAP_REPLY = "STAB_MAP_REPLY";
	
	static public final String RESET_STATE_IN_PROGRESS = "RESET_STATE_IN_PROGRESS";
	
	private int sessionId;
	private String sourceId;
	private String msgId;
	private String msgType;
	private String msg;
	private String forwardType;
	private int localSeqNo;
	private int globalSeqNo;
	private HashMap<String, Integer> stateVector;
	private LinkedHashSet<String> routedRecipients;
	
	/**
	 * Message will always be in this format
	 * [forwardType],[sourceId],[msgId],[msgType],[localSeqNo],[globalSeqNo],[msg]
	 * By default, the source id that is provided will be in the recipient list.
	 * @param forwardType
	 * @param sourceId
	 * @param msgId
	 * @param msgType
	 * @param localSeqNo
	 * @param globalSeqNo
	 * @param msg
	 */
	public Message(int sesstionId, String forwardType, String sourceId, String msgId, String msgType, int localSeqNo, int globalSeqNo, String msg)
	{
		this.sessionId = sesstionId;
		this.forwardType = forwardType;
		this.sourceId = sourceId;
		this.msgId = msgId;
		this.msgType = msgType;
		this.localSeqNo = localSeqNo;
		this.globalSeqNo = globalSeqNo;
		this.msg = msg;
		
		stateVector = new HashMap<String, Integer>();
		
		routedRecipients = new LinkedHashSet<String>();
		addToRoutedList(sourceId);
		
		
	}
	
	public Message(String forwardType, String sourceId, String msgType, int localSeqNo, int globalSeqNo, String msg)
	{
		this(SessionController.getInstance().getSessionId(), forwardType, sourceId, Message.createMsgId(), msgType, localSeqNo, globalSeqNo, msg);
	}
	
	public Message(String forwardType, String sourceId, String msgType, String msg)
	{
		this(SessionController.getInstance().getSessionId(), forwardType, sourceId, Message.createMsgId(), msgType, -1, -1, msg);
	}
	
	public static String createMsgId()
	{
		Random rand = new Random();
		return ""+rand.nextInt();
	}
	
	public static Message createMessage(String rawData)
	{
		try
		{
			StringTokenizer tokens = new StringTokenizer(rawData, ",");
			int sessionId = Integer.parseInt(tokens.nextToken());
			String forwardType = tokens.nextToken();
			String sourceId = tokens.nextToken();
			String msgId = tokens.nextToken();
			String msgType = tokens.nextToken();
			int localSeqNo = Integer.parseInt(tokens.nextToken());
			int globalSeqNo = Integer.parseInt(tokens.nextToken());
			String states = tokens.nextToken();
			String routedList = tokens.nextToken();
			String msg = rawData.substring( (sessionId+","+forwardType+","+sourceId+","+msgId+","+msgType+","+localSeqNo+","+globalSeqNo+","+states+","+routedList+",").length() );
			
//			StringBuffer buffer = new StringBuffer();
//			buffer.append("sessionId: \"" + sessionId + "\"\n");
//			buffer.append("forwardType: \"" + forwardType + "\"\n");
//			buffer.append("sourceId: \"" + sourceId + "\"\n");
//			buffer.append("msgId: \"" + msgId + "\"\n");
//			buffer.append("msgType: \"" + msgType + "\"\n");
//			buffer.append("localSeqNo: \"" + localSeqNo + "\"\n");
//			buffer.append("globalSeqNo: \"" + globalSeqNo + "\"\n");
//			buffer.append("states: \"" + states + "\"\n");
//			buffer.append("routedList: \"" + routedList + "\"\n");
//			buffer.append("msg: \"" + msg + "\"\n");
//			log1.debug(buffer.toString());
			
			Message msgObj = new Message(sessionId, forwardType, sourceId, msgId, msgType, localSeqNo, globalSeqNo, msg);
			
			StringTokenizer routedTokens = new StringTokenizer(routedList, "|");
			while( routedTokens.hasMoreTokens() )
				msgObj.addToRoutedList( routedTokens.nextToken() );
			
			msgObj.setStates(states);
			
			return msgObj;
			
		}catch(Exception e)
		{
			Log log = LogFactory.getLog(Message.class);
			if ( log.isErrorEnabled() ) {
				log.error("Exception", e);
				log.error("rawData: \"" + rawData + "\"");
			}
		}
		return null;
	}
	
	public int getSessionId()
	{
		return sessionId;
	}
	
	public void setSessionId(int sessionId)
	{
		this.sessionId = sessionId;
	}

	public String getSourceId() {
		return sourceId;
	}

	public void setSourceId(String sourceId) {
		this.sourceId = sourceId;
	}

	public String getMsgId() {
		return msgId;
	}

	public void setMsgId(String msgId) {
		this.msgId = msgId;
	}

	public String getMsgType() {
		return msgType;
	}

	public void setMsgType(String msgType) {
		this.msgType = msgType;
	}

	public String getMsg() {
		return msg;
	}

	public void setMsg(String msg) {
		this.msg = msg;
	}

	public String getForwardType() {
		return forwardType;
	}

	public void setForwardType(String forwardType) {
		this.forwardType = forwardType;
	}
	
	public String toString()
	{
		StringBuffer sb = new StringBuffer();
		sb.append(sessionId);
		sb.append(",");
		sb.append(forwardType);
		sb.append(",");
		sb.append(sourceId);
		sb.append(",");
		sb.append(msgId);
		sb.append(",");
		sb.append(msgType);
		sb.append(",");
		sb.append(localSeqNo);
		sb.append(",");
		sb.append(globalSeqNo);
		sb.append(",");
		sb.append(getStates());
		sb.append(",");
		sb.append(getRecipients());
		sb.append(",");
		sb.append(msg);
		
		return sb.toString();
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((msg == null) ? 0 : msg.hashCode());
		result = prime * result + ((msgId == null) ? 0 : msgId.hashCode());
		result = prime * result + ((msgType == null) ? 0 : msgType.hashCode());
		result = prime * result + sessionId;
		result = prime * result
				+ ((sourceId == null) ? 0 : sourceId.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Message other = (Message) obj;
		if (msg == null) {
			if (other.msg != null)
				return false;
		} else if (!msg.equals(other.msg))
			return false;
		if (msgId == null) {
			if (other.msgId != null)
				return false;
		} else if (!msgId.equals(other.msgId))
			return false;
		if (msgType == null) {
			if (other.msgType != null)
				return false;
		} else if (!msgType.equals(other.msgType))
			return false;
		if (sessionId != other.sessionId)
			return false;
		if (sourceId == null) {
			if (other.sourceId != null)
				return false;
		} else if (!sourceId.equals(other.sourceId))
			return false;
		return true;
	}

	public int getLocalSeqNo() {
		return localSeqNo;
	}

	public void setLocalSeqNo(int seqNo) {
		this.localSeqNo = seqNo;
	}

	public int getGlobalSeqNo() {
		return globalSeqNo;
	}

	public void setGlobalSeqNo(int globalSeqNo) {
		this.globalSeqNo = globalSeqNo;
	}
	
	public void addToRoutedList(String routedRecipient)
	{
		routedRecipients.add(routedRecipient);
	}
	
	public boolean containsRoutedRecipient(String routedRecipient)
	{
		return routedRecipients.contains(routedRecipient);
	}
	
	private String getRecipients()
	{
		StringBuffer buffer = new StringBuffer();
		Iterator<String> iter = routedRecipients.iterator();
		while ( iter.hasNext() )
			buffer.append(iter.next()+"|");
		
		return buffer.toString();
	}
	
	public void addState(String nodeId, int msgLocalSeqNo)
	{
		stateVector.put(nodeId, msgLocalSeqNo);
	}
	
	public int getState(String nodeId)
	{
		Integer msgLocalSeqNo = stateVector.get(nodeId);
		if ( msgLocalSeqNo == null )
			return 0;
		return msgLocalSeqNo;
	}
	
	public void setStates(String states)
	{
		if ( states.trim().equals("") )
			return;
		
		stateVector.clear();
		StringTokenizer stateTokens = new StringTokenizer(states, "|");
		while ( stateTokens.hasMoreTokens() )
		{
			StringTokenizer tokens = new StringTokenizer(stateTokens.nextToken(), "~");
			String nodeId = tokens.nextToken();
			int msgLocalSeqNo = Integer.parseInt(tokens.nextToken());
			addState(nodeId, msgLocalSeqNo);
		}
	}
	
	public String getStates()
	{
		if ( stateVector.size() == 0 )
			return " ";
		
		StringBuffer buffer = new StringBuffer();
		Iterator<String> iter = stateVector.keySet().iterator();
		while ( iter.hasNext() )
		{
			String nodeId = iter.next();
			int msgLocalSeqNo = stateVector.get(nodeId);
			buffer.append(nodeId+"~"+msgLocalSeqNo+"|");
		}
		
		return buffer.toString();
	}
	
}//end of class Message
